using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace Core
{
    public class HCProjectManager
    {
        public static HCProject CurrentProject = null;

        /// <summary>
        /// Create a new help collection project
        /// </summary>
        /// <param name="aFullDirectoryPath"></param>
        /// <param name="aProjectName"></param>
        /// <returns></returns>
        public static HCProject CreateProject(string aFullDirectoryPath, string aProjectName, string manifestId)
        {
            HCProject result = new HCProject();

            //Create a folder under the inputted folder
            if (!Directory.Exists(aFullDirectoryPath))
                throw new Exception("Failed to create project, root path do not exists");

            DirectoryInfo parentDi = new DirectoryInfo(aFullDirectoryPath);

            if (Directory.Exists(string.Concat(parentDi.FullName, "\\", aProjectName)))
                throw new Exception(string.Concat("Failed to create project, a directory with name: ", aProjectName, " already exist"));

            DirectoryInfo projectDi = parentDi.CreateSubdirectory(aProjectName);

            //Create manifest file
            string filePath = string.Concat(projectDi.FullName, "\\", manifestId, ".xml");
            HCManifest defaultManifest = new HCManifest();
            defaultManifest.name = aProjectName;
            defaultManifest.id = manifestId;
            defaultManifest.version = "1.1.1.1";
            result.ProjectManifest = defaultManifest;
            string manifestXml = Utils.Serialize(defaultManifest, typeof(HCManifest));

            //Write the manifest file
            using (StreamWriter swrite = new StreamWriter(filePath))
            {
                swrite.Write(manifestXml);
            }

            defaultManifest.FilePath = filePath;

            //Create metadata folder and content folder
            DirectoryInfo contentFolder = projectDi.CreateSubdirectory("content");
            DirectoryInfo metaDataFolder = projectDi.CreateSubdirectory("metadata");

            //Create the project obj
            result.ProjectFolder = projectDi;
            result.ContentFolder = contentFolder;
            result.MetaDataFolder = metaDataFolder;

            HCProjectManager.CurrentProject = result;

            return result;
        }

        /// <summary>
        /// Load the project from disk
        /// </summary>
        /// <param name="directoryPath"></param>
        /// <returns></returns>
        public static HCProject LoadProject(string directoryPath, ref List<string> warningMessages)
        {
            HCProject result = null;

            //Validate the folder
            string errorMessage = string.Empty;
            bool isValid = ValidateProjectFolder(directoryPath, out errorMessage);

            if (isValid)
            {
                result = new HCProject();
                DirectoryInfo projectDi = new DirectoryInfo(directoryPath);
                DirectoryInfo[] contentDi = projectDi.GetDirectories("content");
                if (contentDi == null || contentDi.Length != 1)
                    throw new Exception("Invalid folder structures, there must be exactly one content folder.");
                result.ContentFolder = contentDi[0];

                DirectoryInfo[] metaDataDi = projectDi.GetDirectories("metadata");
                if(metaDataDi == null || metaDataDi.Length != 1)
                    throw new Exception("Invalid folder structures, there must be exactly one metadata folder.");

                result.MetaDataFolder = metaDataDi[0];
                result.ProjectFolder = projectDi;
                
                //Load the manifest file
                FileInfo[] files = projectDi.GetFiles("*.manifest.xml", SearchOption.TopDirectoryOnly);
                FileInfo manifestFile = files[0];
                using (FileStream fs = manifestFile.OpenRead())
                {
                    HCManifest manifest = (HCManifest)Utils.Deserialize(fs, typeof(HCManifest));
                    manifest.FilePath = manifestFile.FullName;
                    result.ProjectManifest = manifest;
                    Utils.UpdateWarningHandler("Done loading project manifest");
                }

                string helpMapFileSearchString = manifestFile.Name.ToLower().Replace(".manifest", string.Empty);
                files = projectDi.GetFiles(helpMapFileSearchString, SearchOption.TopDirectoryOnly);
                if (files.Length > 1)
                {
                    throw new Exception("More than one help map file found!");
                }

                if (files != null && files.Length == 1)
                {
                    using (StreamReader sr = new StreamReader(files[0].FullName))
                    {
                        string rawXml = sr.ReadToEnd();
                        rawXml = rawXml.Replace("<?xml version=\"1.0\" encoding=\"utf-8\"?>", "<?xml version=\"1.0\" ?><HCHelpMapContainer>");
                        rawXml = string.Concat(rawXml, "</HCHelpMapContainer>");

                        MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(rawXml));
                        HCHelpMapContainer hmc = (HCHelpMapContainer)Utils.Deserialize(ms, typeof(HCHelpMapContainer));
                        hmc.FilePath = files[0].FullName;
                        result.HelpMapContainer = hmc;
                        Utils.UpdateWarningHandler("Done loading help map file");
                    }
                }

                //Validate and pull all help items
                foreach (HelpItemManifestEntry helpItemEntry in result.ProjectManifest.helpItemEntries)
                {
                    ValidateAndGetHelpItem(helpItemEntry, result, ref warningMessages);
                }
            }
            else
            {
                throw new Exception(errorMessage);
            }

            HCProjectManager.CurrentProject = result;
            return result;
        }

        private static void ValidateAndGetHelpItem(HelpItemManifestEntry entry, HCProject project, ref List<string> warningMessages)
        {
            Utils.UpdateWarningHandler(string.Concat("Validating and get help item: ", entry.id));
            /*
             * 1. validate this help item has a meta data file
             * 2. open the data file, see what type of entry it is
             * 3. if it is helptopic, helpimage, helpcss or helpscript, make sure it has a content file
             * 4. if all valid, de-serialize the object and assign it to the project object
             */

            //Validate help item meta data file
            string metaDataFileName = string.Concat(entry.id, ".xml");
            FileInfo[] files = project.MetaDataFolder.GetFiles(string.Concat(entry.id, ".xml"), SearchOption.TopDirectoryOnly);

            if (files == null || files.Length != 1)
            { 
                warningMessages.Add(string.Concat("There are must be exactly one meta data file with id: ", entry.id)); 
            }

            FileInfo metaDataFile = files[0];
            using (StreamReader sr = new StreamReader(metaDataFile.OpenRead()))
            {
                string xml = sr.ReadToEnd();
                HelpItem hi = DeserializeHelpItem(xml);

                HelpItemManifestEntry en = project.ProjectManifest.helpItemEntries.Find(delegate(HelpItemManifestEntry item) { return string.Compare(item.id, hi.id, true) == 0; });
                if (hi is HelpCategory)
                {
                    HelpCategory cat = (HelpCategory)hi;
                    cat.ManifestEntry = en;

                    //help category does not require a content file
                    project.HelpCategory.Add(cat);
                }
                else if (hi is HelpContext)
                {
                    //help context does not require a content file
                    HelpContext hc = (HelpContext)hi;
                    hc.ManifestEntry = en;
                    project.HelpContext.Add(hc);
                }
                else if (hi is HelpImage)
                {
                    //verify the image exists
                    string fileNameSearchPattern = string.Concat(hi.id, ".*");
                    FileInfo contentFile = VerifyFileExist(fileNameSearchPattern, project.ContentFolder, ref warningMessages);
                    if (contentFile == null)
                        warningMessages.Add(string.Concat("conten file for help item: ", hi.id, " is not found"));

                    HelpImage himage = (HelpImage)hi;
                    himage.ManifestEntry = en;
                    himage.ContentFile = contentFile;

                    project.HelpImage.Add(himage);
                }
                else if (hi is HelpScript)
                {
                    //verify 
                    string fileNameSearchPattern = string.Concat(hi.id, ".js");
                    FileInfo contentFile = VerifyFileExist(fileNameSearchPattern, project.ContentFolder, ref warningMessages);
                    if (contentFile == null)
                        warningMessages.Add(string.Concat("conten file for help item: ", hi.id, " is not found"));

                    HelpScript hs = (HelpScript)hi;
                    hs.ManifestEntry = en;
                    hs.ContentFile = contentFile;

                    project.HelpScript.Add(hs);
                }
                else if (hi is HelpCss)
                {
                    //verify 
                    string fileNameSearchPattern = string.Concat(hi.id, ".css");
                    FileInfo contentFile = VerifyFileExist(fileNameSearchPattern, project.ContentFolder, ref warningMessages);
                    if (contentFile == null)
                        warningMessages.Add(string.Concat("conten file for help item: ", hi.id, " is not found"));

                    HelpCss hc = (HelpCss)hi;
                    hc.ManifestEntry = en;
                    hc.ContentFile = contentFile;

                    project.HelpCss.Add(hc);
                }
                else if (hi is HelpTopic)
                {
                    //verify 
                    string fileNameSearchPattern = string.Concat(hi.id, ".htm");
                    FileInfo contentFile = VerifyFileExist(fileNameSearchPattern, project.ContentFolder, ref warningMessages);
                    if (contentFile == null)
                        warningMessages.Add(string.Concat("conten file for help item: ", hi.id, " is not found"));

                    HelpTopic ht = (HelpTopic)hi;
                    ht.ManifestEntry = en;
                    ht.ContentFile = contentFile;

                    project.HelpTopic.Add(ht);
                }
            }
        }

        /// <summary>
        /// Verify the file exits and return the file handle
        /// </summary>
        /// <param name="fileNameSearchPattern"></param>
        /// <param name="contentFolder"></param>
        /// <returns></returns>
        private static FileInfo VerifyFileExist(string fileNameSearchPattern, DirectoryInfo contentFolder, ref List<string> warning)
        {
            FileInfo result = null;

            FileInfo[] files = contentFolder.GetFiles(fileNameSearchPattern, SearchOption.TopDirectoryOnly);
            if (files == null || files.Length != 1)
            {
                warning.Add(string.Concat("There must be exactly one file for help item content file : ", fileNameSearchPattern));
                result = null;
            }
            else
                result = files[0];

            return result;
        }

        /// <summary>
        /// Deserialize the 
        /// </summary>
        /// <param name="xml"></param>
        /// <returns></returns>
        private static HelpItem DeserializeHelpItem(string xml)
        {
            HelpItem result = null;

            if (xml.Contains("<helpCategory>"))
            {
                HelpCategory cat = (HelpCategory)Utils.Deserialize(xml, typeof(HelpCategory));
                result = cat;
            }
            else if (xml.Contains("<helpTopic>"))
            {
                HelpTopic topic = (HelpTopic)Utils.Deserialize(xml, typeof(HelpTopic));
                result = topic;
            }
            else if (xml.Contains("<helpContext>"))
            {
                HelpContext context = (HelpContext)Utils.Deserialize(xml, typeof(HelpContext));
                result = context;
            }
            else if (xml.Contains("<helpImage>"))
            {
                HelpImage img = (HelpImage)Utils.Deserialize(xml, typeof(HelpImage));
                result = img;
            }
            else if(xml.Contains("<helpScript>"))
            {
                HelpScript script = (HelpScript)Utils.Deserialize(xml, typeof(HelpScript));
                result = script;
            }
            else if (xml.Contains("<helpCss>"))
            {
                HelpCss css = (HelpCss)Utils.Deserialize(xml, typeof(HelpCss));
                result = css;
            }

            return result;
        }

        /// <summary>
        /// Verify the project folder
        /// </summary>
        /// <param name="folderPath"></param>
        /// <param name="errorMessage"></param>
        /// <returns></returns>
        private static bool ValidateProjectFolder(string folderPath, out string errorMessage)
        {
            errorMessage = string.Empty;
            //Folder must exist
            if (!Directory.Exists(folderPath))
            {
                errorMessage = string.Concat("Failed to load project, folder: ", folderPath, " do not exist");
                return false;
            }

            DirectoryInfo projectDi = new DirectoryInfo(folderPath);

            //Folder must consist of X.manifest.xml file
            FileInfo[] files = projectDi.GetFiles("*.manifest.xml", SearchOption.TopDirectoryOnly);
            if (files.Length != 1)
            {
                errorMessage = "Failed to load project, folder must contain exactly 1 file ends with *.manifest.xml file";
                return false;
            }

            //Folder must have a sub folder called "content"
            DirectoryInfo[] directories = projectDi.GetDirectories("content", SearchOption.TopDirectoryOnly);
            if (directories.Length != 1)
            {
                errorMessage = "Failed to load project, folder must contain a sub folder named \"content\"";
                return false;
            }

            //Folder must have a sub folder call "metadata"
            directories = projectDi.GetDirectories("metadata", SearchOption.TopDirectoryOnly);
            if (directories.Length != 1)
            {
                errorMessage = "Failed to load project, folder must contain a sub folder named \"metadata\"";
                return false;
            }

            return true;
        }

        public static string HcActualInstall(string hcInstallExePath, string localeId, string cabName)
        {
            string result = string.Empty;
            cabName = cabName.Replace(".HC.cab", string.Empty);

            if (HCProjectManager.CurrentProject != null)
            {
                Process proc = new Process();
                proc.StartInfo.FileName = hcInstallExePath;
                proc.StartInfo.Arguments = string.Concat("/act InstallOneHC /loc ", localeId, " /mns ", cabName);
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.UseShellExecute = false;
                proc.StartInfo.RedirectStandardError = true;

                proc.Start();
                proc.WaitForExit();
                result = proc.StandardError.ReadToEnd();
                proc.WaitForExit();
            }

            return result;
        }

        public static string HcAcutalUninstall(string hcInstallExePath, string localeId, string cabName)
        {
            string result = string.Empty;

            cabName = cabName.Replace(".HC.cab", string.Empty);

            if (HCProjectManager.CurrentProject != null)
            {
                Process proc = new Process();
                proc.StartInfo.FileName = hcInstallExePath;
                proc.StartInfo.Arguments = string.Concat("/act UninstallOneHc /loc ", localeId, " /mns ", cabName);
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.UseShellExecute = false;
                proc.StartInfo.RedirectStandardError = true;

                proc.Start();
                proc.WaitForExit();
                result = proc.StandardError.ReadToEnd();
                proc.WaitForExit();
            }

            return result;
        }

        public static string MakeCab(ref string output)
        {
            string result = string.Empty;
            string cabFilePath = string.Empty;

            if (HCProjectManager.CurrentProject != null)
            {
                string rootPath = HCProjectManager.CurrentProject.ProjectFolder.FullName;
                string cabFileName = string.Concat(HCProjectManager.CurrentProject.HCId, ".HC");
                cabFilePath = string.Concat(rootPath, "\\", cabFileName, ".cab");
                if (File.Exists(cabFilePath))
                    File.Delete(cabFilePath);

                string makeCabFiles = string.Concat(rootPath, "\\*.*");
                string exclusePath = rootPath.Replace(Path.GetPathRoot(rootPath), string.Empty);
                exclusePath += "\\";

                exclusePath = string.Concat("\"", exclusePath, "\\\""); //need to append an \ to the end, found out through testing
                result = cabFilePath;
                cabFilePath = string.Concat("\"",cabFilePath,"\"");
                makeCabFiles = string.Concat("\"",makeCabFiles,"\"");

                string command = string.Concat("-p -P ", exclusePath, " -r n ", cabFilePath, " ", makeCabFiles);

                Process proc = new Process();
                proc.StartInfo.FileName = "cabarc.exe";
                proc.StartInfo.Arguments = command;
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.UseShellExecute = false;

                proc.Start();

                proc.WaitForExit();
                output = proc.StandardOutput.ReadToEnd();
                proc.WaitForExit();
            }

            return result;
        }

        
    }
}
